如同範本驅動表單 (Template-Dirven Form),也可以針對響應式表單 (Reactive Form) 進行表單驗證,這一篇就針對待辦事項加入以下的驗證。
在響應式表單 (Reactive Form) 中,無論表單控制項建構式或是 FormBuilder 服務元件的方法,第二與第三的參數皆用來定義一表單驗證陣列,前者為同步型的表單驗證,後者則為非同步。
因此,在 task-form.component.ts 定義表單時,針對主旨與等級加入 required
驗證,並加入主旨與等於的 get 屬性。
export class TaskFormComponent implements OnInit {
get subject(): FormControl {
return this.form.get('subject') as FormControl;
}
get level(): FormControl {
return this.form.get('level') as FormControl;
}
ngOnInit(): void {
this.form = this.fb.group({
subject: this.fb.control(undefined, [Validators.required]),
state: this.fb.control(0),
level: this.fb.control(undefined, [Validators.required]),
tags: this.fb.array([]),
});
}
}
接著就可以在 task-form.component.html 利用 FormControl 的 invalid
與 hasError
來判斷是否顯示錯誤訊息,並在 task-form.component.css 中加入錯誤訊息的樣式。
<form [formGroup]="form">
<div class="form-field">
<label>主旨</label>
<input type="text" formControlName="subject" />
</div>
<ul *ngIf="subject.touched && subject.invalid">
<li *ngIf="subject.hasError('required')">需要輸入主旨</li>
</ul>
<div class="form-field">
<label>等級</label>
<select formControlName="level">
<option value=""></option>
<option value="XS">XS</option>
<option value="S">S</option>
<option value="M">M</option>
<option value="L">L</option>
<option value="XL">XL</option>
</select>
</div>
<ul *ngIf="level.touched && level.invalid">
<li *ngIf="level.hasError('required')">需要輸入等級</li>
</ul>
</form>
ul {
margin: 0;
margin-top: -12px;
padding-left: 100px;
font-size: 10pt;
color: red;
}
當 Angular 所內建的表單驗證無法滿足需求時,可以自訂一表單驗證方法來實作需求,此方法會回傳 ValidationErrors。因此,可以在 task-form.component.ts 加入標籤陣列是否為空的驗證方法,並設定在 tags
表單屬性內。
export class TaskFormComponent implements OnInit {
ngOnInit(): void {
this.form = this.fb.group({
subject: this.fb.control(undefined, [Validators.required]),
state: this.fb.control(0),
level: this.fb.control(undefined, [Validators.required]),
tags: this.fb.array([], [this.arrayCannotEmpty]),
});
}
arrayCannotEmpty(control: FormArray): ValidationErrors {
if (control.length === 0) {
return { cannotEmpty: true };
}
return null;
}
}
在 ArrayCannontEmpty()
的驗證方法中,會傳入所使用表單控制項物料,此案例為表單陣列 tags
,當此陣列為空時回傳一物件,此物件即為 ValidationErrors,會在驗證不通過時將此值寫入表單控制項的 errors
屬性內;因此在通過驗證時,所回傳的值需為 null
。
另外,在需額外指定條件的需求中,例如 'Validators.min()' 表單驗證,可以定義一回傳 ValidatorFn 的驗證方法來實作;因此上面程式可以修改成:
export class TaskFormComponent implements OnInit {
ngOnInit(): void {
this.form = this.fb.group({
subject: this.fb.control(undefined, [Validators.required]),
state: this.fb.control(0),
level: this.fb.control(undefined, [Validators.required]),
tags: this.fb.array([], [this.arrayCannotEmpty()]),
});
}
arrayCannotEmpty(): ValidatorFn {
return (control: FormArray) => {
if (control.length === 0) {
return { cannotEmpty: true };
}
return null;
};
}
}
最後,在頁面上透過是否有 cannotEmpty
錯誤的判斷,來加入標籤陣列的錯誤訊息顯示。
<form [formGroup]="form">
<div class="tags-field">
<button type="button" (click)="onAddTag()">標籤新增</button>
<div formArrayName="tags" *ngFor="let control of tags.controls; let index = index">
<input type="text" [formControl]="control" />
<button type="button" (click)="onDeleteTag(index)">刪除</button>
</div>
</div>
<ul *ngIf="tags.invalid">
<li *ngIf="tags.hasError('cannotEmpty')">標籤不得為空</li>
</ul>
</form>
由於主旨的重覆性檢查,會依後端服務所回傳的結果來判斷,因此需要自訂非同步的驗證方法,此方法會回傳 Observable 型別物件。首先,在 task.service.ts 實作主旨是否已存在的方法。
export class TaskRemoteService {
isExists(subject: string): Observable<boolean> {
const url = `${this._url}?subject=${subject}`;
return this.httpClient.get<Task[]>(url).pipe(map((tasks) => tasks.length > 0));
}
}
接著,在 task-form.component.ts 中加入驗證方法,當值有被輸入時透過 TaskRemoteService 來判斷是否已存在,並將此方法設定至主旨驗證中。
export class TaskFormComponent implements OnInit {
ngOnInit(): void {
this.form = this.fb.group({
subject: this.fb.control(undefined, [Validators.required], [this.shouldBeUnique.bind(this)]),
state: this.fb.control(0),
level: this.fb.control(undefined, [Validators.required]),
tags: this.fb.array([], [this.arrayCannotEmpty]),
});
}
shouldBeUnique(control: FormControl): Observable<ValidationErrors> {
if (control.value) {
return this.taskService.isExists(control.value).pipe(map((isExists) => (isExists ? { shouldBeUnique: true } : null)));
} else {
return of(null);
}
}
}
最後,在 task-form.component.html 中加入錯誤訊息的顯示。
<form [formGroup]="form">
<div class="form-field">
<label>主旨</label>
<input type="text" formControlName="subject" />
</div>
<ul *ngIf="subject.touched && subject.invalid">
<li *ngIf="subject.hasError('required')">需要輸入主旨</li>
<li *ngIf="subject.hasError('shouldBeUnique')">主旨已存在</li>
</ul>
</form>
透過響應式表單 (Reactive Form) 的開發方法,再加上 Rxjs 的使用,針對較為複雜的表單,會有很大彈性的靈活度。